package controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import misc.Configurations;
import misc.Logger;
import misc.Utility;
import misc.Logger.LogType;
import data.AuthorizationSession;
import data.DatabaseGateway;
import data.DownloadLog;
import data.File;
import data.IPServing;
import data.UploadLog;
import controller.FileDeletionController;

public class DownloadController extends HttpServlet{
    public File retrieveFile(String hashedcode)
    {
        try
        {
            String query = 
                    "SELECT * FROM FILES WHERE HashedCode='"+hashedcode+"'";

            DatabaseGateway gw = new DatabaseGateway();
            gw.openConnection(Configurations.User,Configurations.Password,
                                    Configurations.DatabaseName);
            int numRows=gw.executeCount(query);

            if(numRows<=0)
                return null;

            ResultSet rs = gw.executeQuery(query);

            File new_file = new File();
            rs.next();//Move into first row
            new_file.setFileID(rs.getLong("FileID"));
            new_file.setFilepath(rs.getString("FilePath"));
            new_file.setProtected(rs.getBoolean("isProtected"));
            new_file.setDeleted(rs.getBoolean("isDeleted"));
            new_file.setName(rs.getString("OriginalName"));
            new_file.setSize(rs.getLong("Size"));
            new_file.setHashedPassword(rs.getString("HashedPassword"));
            new_file.setHashedCode(rs.getString("HashedCode"));
            new_file.setType(rs.getString("Type"));
            gw.closeConnection();

            return new_file;
        }
        catch(Exception e)
        {
            Logger.write(e.getMessage(),Logger.LogType.error);
        }

        return null;
    }

    public UploadLog retrieveUploadLog(long id)
    {
        try
        {
            String query = "SELECT * FROM UPLOADLOGS WHERE FileID='"+id+"'";

            DatabaseGateway gw = new DatabaseGateway();
            gw.openConnection(Configurations.User,Configurations.Password,
                                    Configurations.DatabaseName);
            int numRows=gw.executeCount(query);

            if(numRows<=0)
                return null;

            ResultSet rs = gw.executeQuery(query);

            UploadLog new_log = new UploadLog();
            rs.next();//Move into first row
            new_log.setStart(rs.getTimestamp("StartTimeDate"));
            new_log.setFinish(rs.getTimestamp("FinishTimeDate"));
            new_log.setUsername(rs.getString("Username"));
            gw.closeConnection();

            return new_log;
        }
        catch(Exception e)
        {
        Logger.write(e.getMessage(),Logger.LogType.error);
        }

        return null;
    }

    public IPServing retrieveIPServing(String ip)
    {
        try
        {
            String query = "SELECT * FROM IPSERVING WHERE IP='"+ip+"'";

            DatabaseGateway gw = new DatabaseGateway();
            gw.openConnection(Configurations.User,Configurations.Password,
                                        Configurations.DatabaseName);
            int numRows=gw.executeCount(query);

            if(numRows<=0)
            return null;

            ResultSet rs = gw.executeQuery(query);

            IPServing new_log = new IPServing();
            rs.next();//Move into first row
            new_log.setIPaddress(rs.getString("IP"));
            new_log.setDownloadingFileID(rs.getLong("DownloadingFileID"));
            gw.closeConnection();

            return new_log;
        }
        catch(Exception e)
        {
            Logger.write(e.getMessage(),Logger.LogType.error);
        }

        return null;
    }

    //Insert the download information of each download
    private void insert_DownloadRecords(DownloadLog this_log)
    {
        try
        {
        String query = "INSERT INTO DOWNLOADLOGS(IP,FileID,StartTimeDate,"+
                "FinishTimeDate,Username) VALUES('"+this_log.getIPaddress()+
                "','"+this_log.getFileID()+"','"+
        this_log.getStart().toString()+"','"+
        this_log.getFinish().toString()+"',";
        if(this_log.getUsername()==null)
        query+="null)";
        else
        query+="'"+this_log.getUsername()+"')";

        DatabaseGateway gw = new DatabaseGateway();
        gw.openConnection(Configurations.User,Configurations.Password,
                                        Configurations.DatabaseName);
        gw.startTransaction();
        gw.executeUpdate(query);
        gw.commitTransaction();
        gw.closeConnection();
        }
        catch(Exception e)
        {
        Logger.write(e.getMessage(),Logger.LogType.error);
        }
    }

    //When download is completed or interrupted, update the finish time of
    //the download log
    private void update_DownloadRecords(long fileid,Timestamp startTime,
                                        String ip,Timestamp finishTime)
    {
        try
        {
            String query1 = 
                "SELECT ID FROM DOWNLOADLOGS WHERE StartTimeDate='"+startTime+
                "' AND FileID='"+fileid+"' AND IP='"+ip+"'";
            String query2 = 
                "UPDATE DOWNLOADLOGS SET StartTimeDate='"+startTime+
                "', FinishTimeDate= '"+finishTime+"' WHERE ID='";
                //For unknown reason, database updates BOTH times if finish
                //time is just involved
            DatabaseGateway gw = new DatabaseGateway();
            gw.openConnection(Configurations.User,Configurations.Password,
                                    Configurations.DatabaseName);
            gw.startTransaction();
            ResultSet rs = gw.executeQuery(query1);
            rs.next();query2=query2+rs.getInt("ID")+"'";
            gw.executeUpdate(query2);
            gw.commitTransaction();
            gw.closeConnection();
        }
        catch(Exception e)
        {
            Logger.write(e.getMessage(),Logger.LogType.error);
        }
    }

    //When start download, insert the ip of
    //the downloader to the temporary table 
    private void insert_IPRecords(IPServing this_log)
    {
        try
        {
            String query = "INSERT INTO IPSERVING(IP,DownloadingFileID) "+
                            "VALUES('"+this_log.getIPaddress()+"','"+
                            this_log.getDownloadingFileID()+"')";

            DatabaseGateway gw = new DatabaseGateway();
            gw.openConnection(Configurations.User,Configurations.Password,
                                                Configurations.DatabaseName);
            gw.executeUpdate(query);
            gw.closeConnection();
        }
        catch(Exception e)
        {
            Logger.write(e.getMessage(),Logger.LogType.error);
        }
    }

    //Delete the IP information of the downloader when he finishes the download
    private void delete_IPRecords(IPServing this_log)
    {
        try
        {
            String query = 
              "Delete From IPSERVING WHERE IP = '"+this_log.getIPaddress()+"'";
            DatabaseGateway gw = new DatabaseGateway();
            gw.openConnection(Configurations.User,Configurations.Password,
                                Configurations.DatabaseName);
            gw.startTransaction();
            gw.executeUpdate(query);
            gw.commitTransaction();
            gw.closeConnection();
        }
        catch(Exception e)
        {
            Logger.write(e.getMessage(),Logger.LogType.error);
        }
    }

    //Check the input password to download protected files 
    public boolean isAuthorized(String password,String hashedcode)
    {
        data.File this_file = retrieveFile(hashedcode);

        if(Utility.hashMD5(password).equals(this_file.getHashedPassword()))
            return true;
        else
            return false;
    }

    //Check the file is deleted or not
    public boolean deleted_file(String hashedcode)
    {
        File this_file = retrieveFile(hashedcode);
        //UploadLog this_log = retrieveUploadLog(this_file.getFileID());
        if(this_file.isDeleted()==true ||
                    isFileBeingPended(this_file.getFileID()))
            return true;
        //else if(this_log.getUsername()!=null)
        //    return false;

        return false;
    }
    
    //Check if the file is on pending list to be deleted automatically
    private boolean isFileBeingPended(long fileID)
    {
        try
        {
            String query="SELECT * FROM PENDINGFILES WHERE FileID="+fileID;
            DatabaseGateway gw = new DatabaseGateway();
            gw.openConnection(Configurations.User,Configurations.Password,
                                Configurations.DatabaseName);
            int c=gw.executeCount(query);
            gw.closeConnection();
            return (c>0);
        }
        catch(Exception e)
        {
            Logger.write(e.getMessage(), LogType.error);
        }
        
        return false;
    }

    
    //Check if particular file uploaded by unregistered user is on server
    //for more than predetermined time
    public boolean is_expired(String hashedcode)
    {
        File this_file = retrieveFile(hashedcode);
        UploadLog this_log = retrieveUploadLog(this_file.getFileID());
        Timestamp uploadedTS=this_log.getFinish();
        Timestamp nowTS = 
                new Timestamp(Calendar.getInstance().getTime().getTime());

        //i.e. the file was uploaded by registered user
        //if(this_log.getUsername()!=null)
            //return false;

        //getTime() return time in milliseconds
        if((nowTS.getTime()-uploadedTS.getTime())/1000 >=
                Configurations.AvailableTime*60)
            return true;

        return false;
    }

    //Check the download times of particular file uploaded by unregistered user
    public boolean download_times_out(long id) throws Exception{
        //UploadLog this_log = retrieveUploadLog(id);

        //if(this_log.getUsername()!=null)
            //return false;

        String query1 = "SELECT * FROM DOWNLOADLOGS WHERE FileID='"+id+"'";
                                                    //"and Username is null";
        DatabaseGateway gw = new DatabaseGateway();
        gw.openConnection(Configurations.User,Configurations.Password,
                                Configurations.DatabaseName);
        int download_num = gw.executeCount(query1);
        if(download_num>=Configurations.MaxDownloadsPerFile){
            return true;
        }

        return false;
    }

    //Check whether unregistered user is downloading a file at one time
    public boolean is_one_file_a_time(String username,String ipaddress)
    {
        if(username==null)
        {
            try
            {
                String query = 
                            "SELECT * FROM IPSERVING WHERE IP='"+ipaddress+"'";
                DatabaseGateway gw = new DatabaseGateway();
                gw.openConnection(Configurations.User,Configurations.Password,
                                                Configurations.DatabaseName);
                int numRows=gw.executeCount(query);

                if(numRows>=Configurations.MaxConcurrentDownloadings)
                    return false;
            }
            catch(Exception e)
            {
                Logger.write(e.getMessage(),Logger.LogType.error);
            }
        }

        return true;
    }

    //Complex operation
    public void doFileDownload(String hashedcode,HttpServletRequest request,
                            HttpServletResponse response)
    {
        try{
            File this_file = retrieveFile(hashedcode);
            Timestamp startTime = new Timestamp(new Date().getTime());

            IPServing this_iplog = constructip(this_file.getFileID(),
                                                    request.getRemoteAddr());

            //Add to ip list of downloading atm
            insert_IPRecords(this_iplog);

            //If file is picture
            if(this_file.getType().indexOf("jpeg")>=0 ||
                this_file.getType().indexOf("png")>=0 ||
                this_file.getType().indexOf("jpg")>=0 ||
                this_file.getType().indexOf("gif")>=0 ||
                this_file.getType().indexOf("bmp")>=0)
            {
                //Sending signal to display-stream instead of data transfer
                response.setContentType(this_file.getType());
                response.setHeader("Content-disposition", 
                        "filename=" + this_file.getName());
            }
            else
            {
                //Data transferring
                response.setContentType("application/octet-stream");
                response.setHeader("Content-disposition", 
                            "attachment;filename=" +this_file.getName());
            }

            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;

            try
            {
                bis = new BufferedInputStream(new FileInputStream
                                                (this_file.getFilepath()));
                bos = new BufferedOutputStream(response.getOutputStream());

                DownloadLog this_log = construct(this_file.getFileID(),
                    request.getRemoteAddr(),getUsername(request),startTime);
                    
                insert_DownloadRecords(this_log);

                byte[] buff = new byte[2048];
                int bytesRead;
                while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
                    bos.write(buff, 0, bytesRead);

                    //Simulate "slow" download rate
                    if(Configurations.setSlowDownload)
                        Thread.sleep(Configurations.timedelay);
                }
            }
            catch(IOException e)
            {
                //User cancelled the downloading
                //Do nothing
            }
            finally
            {
                if (bis != null)
                    bis.close();
                if (bos != null)
                    bos.close();
                    
                Timestamp finishTime = new Timestamp(new Date().getTime());
                update_DownloadRecords(this_file.getFileID(),startTime,
                                        request.getRemoteAddr(),finishTime);

                //Remove from ip list of downloading atm
                delete_IPRecords(this_iplog);
                
                //Call function to purge pending files
                FileDeletionController fileControl = 
                                                new FileDeletionController();
                fileControl.purgePendingFile(this_file.getFileID());
            }

        }
        catch(Exception e)
        {
            Logger.write(e.getMessage(),Logger.LogType.error);
        }
    }

    //Construct data structure of DownloadLog
    private DownloadLog construct(long fileid,String address,
                                    String username,Timestamp startTime)
    {
        DownloadLog this_log = new DownloadLog();
        this_log.setFileID(fileid);
        this_log.setIPaddress(address);
        this_log.setStart(startTime);
        this_log.setFinish(startTime);
        this_log.setUsername(username);

        return this_log;
    }

    //Construct data structure of IPServing
    private IPServing constructip(long fileid,String address)
    {
        IPServing this_log = new IPServing();
        this_log.setDownloadingFileID(fileid);
        this_log.setIPaddress(address);

        return this_log;
    }

    //Return null (unregistered user) or username (retrieved from Server 
    //Session State, stored by logging process)
    public String getUsername(HttpServletRequest request) throws Exception
    {
        if(AuthorizationSession.checkAuthorizationToken(request, null, false))
        {
            HttpSession session = request.getSession();
            if(session.getAttribute("name")==null)
                throw new Exception("Session-Cannot retrieve name attribute!");

            return (String)session.getAttribute("name");
        }

        return null;
    }
}